#include "stdafx.h"

#include "Routines.h"
#include "OSException.h"
#include "ScopedHandle.h"
#include "Logging.h"
#include "../Common/Compression/Compress.h"

String WorkingDirectory;
String ApplicationDirectory;
String Debugee;

namespace {

struct FileInfo
{
    String Name;
    size_t Offset;
    size_t Length;
    FileInfo ()
        : Offset (0), Length (0)
    { }
};

void SetTemporaryDirectory ()
{
    _TCHAR Buffer [MAX_PATH] = {0};
    GetTempPath (MAX_PATH, Buffer);

    WorkingDirectory = PathAppend (Buffer, CreateGuid());

    if (!CreateDirectory (WorkingDirectory.c_str (), 0)) {
        WorkingDirectory.clear ();
        RaiseOsError ("unable to create working directory");
    }
}

std::list<FileInfo> ExtractFileInfos (HANDLE aInput)
{
    char Buffer[MAX_PATH] = {0};

    DWORD Count = 0;
    DWORD FirstOffset = 0;
    DWORD Read = 0;

    SetFilePointer(aInput, -26, 0, FILE_END);

    if (!ReadFile(aInput, Buffer, MAX_PATH, &Read, 0)) {
        RaiseOsError("unable to read file tail");
    }

    sscanf (Buffer, "%d %d", &Count, &FirstOffset);

    SetFilePointer(aInput, FirstOffset, 0, FILE_BEGIN);
    std::list<FileInfo> Result;

    for (DWORD Index = 0; Index < Count; ++Index) {
        int Indicator = 0;
        char Item;
        FileInfo Info;
        while (Indicator < 2) {
            if (!ReadFile(aInput, &Item, 1, &Read, 0) || Read < 1) {
                RaiseOsError("unable to read char from file");
            }
            if (Item == '"') {
                ++Indicator;
            }
            else {
                Info.Name += Item;
            }
        }
        if (!ReadFile(aInput, &Buffer, 21, &Read, 0) || Read < 21) {
            RaiseOsError("unable to read offset / size block");
        }
        sscanf (Buffer, "%d %d", &Info.Offset, &Info.Length);
        Result.push_back (Info);
    }

    return Result;
}

void UnpackFilesToTemporary (HANDLE aInput, std::list<FileInfo>& aInfos)
{
    auto Start = aInfos.begin();
    auto Eol = aInfos.end();

    while (Start != Eol) {
        SetFilePointer (aInput, Start->Offset, 0, FILE_BEGIN);
        String OutputName = PathAppend (WorkingDirectory, Start->Name);
        String Target = OutputName;
        OutputName += ".compressed";
        {
        ScopedHandle Output = CreateFile (OutputName.c_str (), GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
        char Buffer[2048] = {0};
        DWORD Moved;
        do {
            DWORD Move = min (Start->Length, 2048);
            Start->Length -= Move;
            if (!ReadFile(aInput, Buffer, Move, &Moved, 0)) {
                RaiseOsError("unable to read file block");
            }
            if (!WriteFile (Output (), Buffer, Moved, &Moved, 0)) {
                RaiseOsError("unable to write file block");
            }
        } while (Start->Length > 0);
        }

        if (DecodeFile(OutputName.c_str(), Target.c_str()) != 0) {
            throw std::runtime_error("unable to unpack file");
        }
        ++Start;
    }
}

void UnpackToTemporary (HANDLE aInput, std::list<String>& aFiles)
{
    std::list<FileInfo> Infos = ExtractFileInfos (aInput);
    UnpackFilesToTemporary (aInput, Infos);
}

}

void UnpackSelf (std::list<String>& aFiles)
{
    Log("Extracting setup files - enter");
    SetTemporaryDirectory ();

    _TCHAR Self[MAX_PATH] = {0};

    if (Debugee.empty ()) {
        if (!GetModuleFileName (NULL, Self, MAX_PATH)) {
            RaiseOsError(TEXT("Failed to get full path to self"));
        }

        TCHAR Root[8] = {0};
        TCHAR Directory[MAX_PATH] = {0};
        TCHAR FileName[MAX_PATH] = {0};

        _tsplitpath(Self, Root, Directory, 0, 0);

        ApplicationDirectory = PathAppend(Root, Directory);
    }
    else {
        _tcscpy (Self, Debugee.c_str ());
    }

    ScopedHandle SelfStream = CreateFile (Self, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
    if (SelfStream () == INVALID_HANDLE_VALUE) {
        RaiseOsError (TEXT("Failed to get handle to self"));
    }

    SetFilePointer(SelfStream (), -4, 0, FILE_END);

    char Buffer[5] = {0};
    DWORD Read = 0;
    if (!ReadFile (SelfStream (), Buffer, 4, &Read, 0)) {
        RaiseOsError (TEXT("Failed to read debug indicator"));
    }

    if (_tcscmp (Buffer, "OLJA") == 0) {
        UnpackToTemporary (SelfStream (), aFiles);
    }

    Log("Extracting setup files - exit");
}

